import { vueArrayProto } from './array'
import Dep from './dep'

export function observe(value) {
  // 如果是一个基本类型，那么就不观察
  if (value === null || typeof value !== 'object') return

  let ob
  // 如果有，就直接拿
  if (value.__ob__) {
    ob = value.__ob__
  }
  // 如果没有，再去 new
  else {
    ob = new Observer(value) // user: { name: 'zs', age:  }
  }
  return ob
}

// 观察者
class Observer {
  // 构造函数
  constructor(value) {
    console.count('Observer count:')

    // 这个 this 是观察者实例
    // value.__ob__ = this
    // Object.keys 会将所有可枚举的键拿出来，其实 __ob__ 是我们的一个私有属性，用来保存 Observer 实例，不需要去做数据劫持
    Object.defineProperty(value, '__ob__', {
      value: this,
      enumerable: false,
    })

    if (Array.isArray(value)) {
      // 数组的.__proto__ ==> Array.prototype
      // 数组的.__proto__ ==> Vue自己的数组原型
      // Vue自己的数组原型.__proto__ ==> Array.prototype
      value.__proto__ = vueArrayProto
      // 监听数组中的每一个元素
      observeArray(value)
    } else {
      // 说明是对象，走起
      this.walk(value)
    }
  }

  walk(value) {
    for (const k of Object.keys(value)) {
      defineReactive(value, k)
    }
  }
}

// 我们自己定义一个函数去处理
function defineReactive(obj, k, v) {
  // 这个维护一个闭包的 dep，用来管理这个 k 的依赖
  const dep = new Dep()

  // 如果只传了两个参数，那么就自动获取v的值
  // arguments 实参列表伪数组
  if (arguments.length === 2) {
    v = obj[k]
  }

  // 递归嵌套观察
  // 假设 obj => { count: 0, user: { name: 'zs', age: 18 } }, k => user
  // obj[k] ==> v ==> { name: 'zs', age: 18 }
  observe(v)

  // 这里使用 v 这个闭包变量来保存当前的 value 值
  Object.defineProperty(obj, k, {
    get() {
      console.log('在访问', k)
      // 收集依赖
      dep.depend()
      return v
    },
    set(newValue) {
      console.log('在设置', k, '，新值：', newValue)
      v = newValue
      // 通知更新
      dep.notify()
    },
  })
}

export function observeArray(array) {
  for (const v of array) {
    observe(v)
  }
}
